#include "../include/decoder/ffmpeg_decoder.h"

FFmpegDecoder::FFmpegDecoder(CODEC_TYPE p_codec_id) :
	codec_id(p_codec_id),
	first_time(true),
	y_size(0)
{
}

FFmpegDecoder::~FFmpegDecoder() {
}

int FFmpegDecoder::init() {

	avcodec_register_all();
	AVCodecID av_codec_id;
	switch(codec_id) {
	case CODEC_TYPE_H264_CPU:
		av_codec_id = AV_CODEC_ID_H264;
		break;
	default:
		av_codec_id = AV_CODEC_ID_H264;
		break;
	}
	pCodec = avcodec_find_decoder(av_codec_id);
	if (!pCodec) {
//		printf("Codec not found\n");
		return CODEC_ERROR_FFMPEG_DECODER_CODEC_NOT_FIND;
	}

	pCodecCtx = avcodec_alloc_context3(pCodec);
	if (!pCodecCtx) {
//		printf("Could not allocate video codec context\n");
		return CODEC_ERROR_FFMPEG_DECODER_CONTEXT_NOT_ALLOCATE;
	}

	pCodecParserCtx = av_parser_init(av_codec_id);
	if (!pCodecParserCtx) {
		printf("Could not allocate video parser context\n");
		return -1;
	}

	if (pCodec->capabilities & CODEC_CAP_TRUNCATED)
		pCodecCtx->flags |= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
//		printf("Could not open codec\n");
		return CODEC_ERROR_FFMPEG_DECODER_CONTEXT_NOT_OPEN;
	}

	pFrame = av_frame_alloc();
	av_init_packet(&packet);

	return CODEC_OK;
}

int FFmpegDecoder::decodeFrame(Mat& frame, int& gotFrame, unsigned char* encode_buffer,
				int buffer_length, int& used_length) {

	unsigned char* p_encode_buf = encode_buffer;
	int p_used_buf_length = 0;
	used_length = 0;

	while(buffer_length > 0) {

		p_used_buf_length = av_parser_parse2(pCodecParserCtx, pCodecCtx, &packet.data,
								&packet.size, p_encode_buf, buffer_length,
								AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

		used_length += p_used_buf_length;
		buffer_length -= p_used_buf_length;
		p_encode_buf += p_used_buf_length;

		if (packet.size == 0)
			return CODEC_ERROR_FFMPEG_DECODER_DECODE_FAILED;

		int ret = avcodec_decode_video2(pCodecCtx, pFrame, &gotFrame,
				&packet);
		if (ret < 0) {
			//log
			return CODEC_ERROR_FFMPEG_DECODER_DECODE_FAILED;
		}

		if (gotFrame) {
			if (first_time) {
				img_convert_ctx = sws_getContext(pCodecCtx->width,
						pCodecCtx->height, pCodecCtx->pix_fmt,
						pCodecCtx->width, pCodecCtx->height,
						AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
				pFrameYUV = av_frame_alloc();
				out_buffer = (uint8_t *) av_malloc(
						avpicture_get_size(AV_PIX_FMT_YUV420P,
								pCodecCtx->width, pCodecCtx->height));
				avpicture_fill((AVPicture *) pFrameYUV, out_buffer,
						AV_PIX_FMT_YUV420P, pCodecCtx->width,
						pCodecCtx->height);
				y_size = pCodecCtx->width * pCodecCtx->height;
				first_time = false;
			}
			sws_scale(img_convert_ctx,
					(const uint8_t* const *) pFrame->data, pFrame->linesize,
					0, pCodecCtx->height, pFrameYUV->data,
					pFrameYUV->linesize);

			unsigned char* decode_data = new unsigned char[y_size * 3 / 2];
			if(!decode_data) {
				//log
				return CODEC_ERROR_NO_MEM_AVALIABLE;
			}

			memcpy(decode_data, pFrameYUV->data[0], y_size);
			memcpy(decode_data + y_size, pFrameYUV->data[1], y_size / 4);
			memcpy(decode_data + y_size * 5 / 4, pFrameYUV->data[2], y_size / 4);

			frame.data = decode_data;
			break;
		}
	}
	return CODEC_OK;
}

void FFmpegDecoder::close() {
	sws_freeContext(img_convert_ctx);
	av_parser_close(pCodecParserCtx);
	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	av_free(pCodecCtx);
}
